/* Copyright (c) 2015,2016 Lucky Byte, Inc.
 */
package com.lucky_byte.pay.jar.lualib;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Varargs;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.TwoArgFunction;
import org.luaj.vm2.lib.VarArgFunction;
import org.luaj.vm2.lib.ZeroArgFunction;

import com.lucky_byte.pay.jar.Jdbc;
import com.lucky_byte.pay.jar.JdbcRecord;
import com.lucky_byte.pay.jar.JdbcTable;

import org.luaj.vm2.LuaTable;

/**
 * Lua 访问数据库接口
 */
public class LuaJdbc extends TwoArgFunction
{
	private static final Logger logger = LogManager.getLogger();

	public LuaJdbc() {
	}

	@Override
	public LuaValue call(LuaValue modname, LuaValue env) {
		LuaValue library = tableOf();
		env.set("jdbc", library);

		switch (Jdbc.getDBDriver()) {
		case Jdbc.DBDRI_PGSQL:
			library.set("driver", LuaValue.valueOf("PostgreSQL"));
			break;
		}
		library.set("select", new Select());
		library.set("update", new Update());
		library.set("upsert", new Upsert());

		return library;
	}

	/**
	 * 取函数参数，转换为 SQL 对象
	 */
	private List<Object> getParams(Varargs args, int start) {
		List<Object> params = new ArrayList<>();
		for (int n = start; n <= args.narg(); n++) {
			LuaValue arg = args.checkvalue(n);
			params.add(LuaHelper.value2Object(arg));
		}
		return params;
	}

	/**
	 * 查询数据库，返回 JdbcTable 对象
	 */
	class Select extends VarArgFunction
	{
		@Override
		public Varargs invoke(Varargs args) {
			String sql = args.checkjstring(1);
			List<Object> params = getParams(args, 2);
			try {
				JdbcTable table = JdbcTable.query(sql, params);
				LuaValue result = tableOf();
				result.set("count", LuaValue.valueOf(table.getRecordCount()));
				result.set("get_record", new TableGetRecord(table));
				result.set("to_table", new TableToLuaTable(table));
				return result;
			} catch (SQLException e) {
				logger.catching(e);
				logger.error("执行 SQL 语句[{}]错误[{}].", sql, e);
				return LuaValue.NIL;
			}
		}
	}

	/**
	 * 查询记录
	 */
	class TableGetRecord extends OneArgFunction
	{
		private final JdbcTable table;

		public TableGetRecord(JdbcTable table) {
			this.table = table;
		}

		@Override
		public LuaValue call(LuaValue arg) {
			int index = arg.checkint();
			JdbcRecord record = table.getRecord(index);
			if (record == null) {
				return LuaValue.NIL;
			}
			return new LuaJdbcRecord(record).call();
		}
	}

	/**
	 * 转换为 Lua Table
	 */
	class TableToLuaTable extends ZeroArgFunction
	{
		private final JdbcTable table;

		public TableToLuaTable(JdbcTable table) {
			this.table = table;
		}

		@Override
		public LuaValue call() {
			LuaTable lua_table = LuaValue.tableOf();
			for (int i = 0; i < table.getRecordCount(); i++) {
				JdbcRecord record = table.getRecord(i);
				lua_table.set(i + 1, new LuaJdbcRecord(record).call());
			}
			return lua_table;
		}
	}

	/**
	 * 执行 SQL 更新语句
	 */
	class Update extends VarArgFunction
	{
		private int result;

		@Override
		public Varargs invoke(Varargs args) {
			String sql = args.checkjstring(1);
			List<Object> params = getParams(args, 2);
			try {
				result = -1;
				Jdbc.update(sql, params, new Jdbc.UpdateListener() {
					@Override
					public void onUpdate(int count) {
						result = count;
					}
				});
				return LuaValue.valueOf(result);
			} catch (SQLException e) {
				logger.error("执行SQL语句[{}]错误[{}].", sql, e.getMessage());
				return LuaValue.valueOf(-1);
			}
		}
	}

	/**
	 * 插入一条数据库记录或更新现有的记录, 数据以 Lua Table 传递
	 */
	class Upsert extends VarArgFunction
	{
		@Override
		public Varargs invoke(Varargs args) {
			String table_name = args.checkjstring(1);
			LuaTable record_table = args.checktable(2);

			// 转换 LuaTable 为数据库记录
			JdbcRecord record = new JdbcRecord();
			LuaValue k = LuaValue.NIL;
			while (true) {
				Varargs kv = record_table.next(k);
				k = kv.arg1();
				if (k.isnil()) {
					break;
				}
				LuaValue v = kv.arg(2);
				record.setField(k.tojstring(), LuaHelper.value2Object(v));
			}
			// 准备条件字段列表(如果有的话)，条件字段可用于更新记录
			List<String> keys = new ArrayList<>();
			if (args.istable(3)) {
				LuaTable keys_table = args.checktable(3);
				for (int i = 1; ; i++) {
					LuaValue v = keys_table.get(i);
					if (v.isnil()) {
						break;
					}
					keys.add(v.tojstring());
				}
			} else {
				for (int i = 3; ; i++) {
					if (args.isnil(i)) {
						break;
					}
					keys.add(args.tojstring(i));
				}
			}
			try {
				// 如果提供了条件字段，则先尝试更新记录，如果没有可更新记录，则插入
				if (keys.size() > 0) {
					JdbcTable.upsert(table_name, record, keys);
				} else {
					JdbcTable.insert(table_name, record);
				}
				return LuaValue.valueOf(1);
			} catch (SQLException e) {
				logger.error("插入数据表[{}]错误[{}].", table_name, e.getMessage());
				return LuaValue.valueOf(-1);
			}
		}
	}
}
